nexus\api\event/
mod.rs

1//! Event system.
2//!
3//! ```no_run
4//! use nexus::{
5//!     event::{ADDON_LOADED, event_consume},
6//!     log::{log, LogLevel}
7//! };
8//! use std::ptr::NonNull;
9//!
10//! let callback = event_consume!(|payload: Option<&i32>| {
11//!     if let Some(signature) = payload {
12//!         log(LogLevel::Info, "My Addon", format!("Addon {signature} loaded"));
13//!     }
14//! });
15//!
16//! ADDON_LOADED.subscribe(callback);
17//! ```
18
19mod nexus;
20
21#[cfg(feature = "arc")]
22pub mod arc;
23
24#[cfg(feature = "extras")]
25pub mod extras;
26
27#[cfg(feature = "rtapi")]
28pub mod rtapi;
29
30use super::EventApi;
31use crate::{revertible::Revertible, util::str_to_c, AddonApi};
32use std::{
33    ffi::{c_char, c_void},
34    marker::PhantomData,
35    mem,
36};
37
38pub use self::nexus::*;
39
40/// An event identifier & payload type pair.
41#[derive(Debug, Clone, Copy)]
42pub struct Event<T> {
43    pub identifier: &'static str,
44    _phantom: PhantomData<T>,
45}
46
47impl<T> Event<T> {
48    /// Creates a new event identifier & payload type pair.
49    ///
50    /// # Safety
51    /// See [`event_subscribe_typed`].
52    #[inline]
53    pub const unsafe fn new(identifier: &'static str) -> Self {
54        Self {
55            identifier,
56            _phantom: PhantomData,
57        }
58    }
59
60    /// Subscribes to the event.
61    #[inline]
62    pub fn subscribe(
63        &self,
64        callback: RawEventConsume<T>,
65    ) -> Revertible<impl Fn() + Send + Sync + Clone + 'static> {
66        unsafe { event_subscribe_typed(self.identifier, callback) }
67    }
68
69    /// Raises the event.
70    #[inline]
71    pub fn raise(&self, event_data: &T) {
72        unsafe { event_raise(self.identifier, event_data) }
73    }
74}
75
76pub type RawEventConsume<T> = extern "C-unwind" fn(event_args: *const T);
77
78pub type RawEventConsumeUnknown = RawEventConsume<c_void>;
79
80pub type RawEventRaise =
81    unsafe extern "C-unwind" fn(identifier: *const c_char, event_data: *const c_void);
82
83pub type RawEventRaiseNotification = unsafe extern "C-unwind" fn(identifier: *const c_char);
84
85pub type RawEventRaiseTargeted = unsafe extern "C-unwind" fn(
86    signature: i32,
87    identifier: *const c_char,
88    event_data: *const c_void,
89);
90
91pub type RawEventRaiseNotificationTargeted =
92    unsafe extern "C-unwind" fn(signature: i32, identifier: *const c_char);
93
94pub type RawEventSubscribe = unsafe extern "C-unwind" fn(
95    identifier: *const c_char,
96    consume_callback: RawEventConsumeUnknown,
97);
98
99/// Subscribes to an event with a raw callback using an unknown payload.
100///
101/// Returns a [`Revertible`] to revert the subscribe.
102pub fn event_subscribe_unknown(
103    identifier: impl AsRef<str>,
104    callback: RawEventConsumeUnknown,
105) -> Revertible<impl Fn() + Send + Sync + Clone + 'static> {
106    let identifier = str_to_c(identifier, "failed to convert event identifier");
107    let EventApi {
108        subscribe,
109        unsubscribe,
110        ..
111    } = AddonApi::get().event;
112    unsafe { subscribe(identifier.as_ptr(), callback) };
113    let revert = move || unsafe { unsubscribe(identifier.as_ptr(), callback) };
114    revert.into()
115}
116
117/// Subscribes to an event with a raw callback using a typed payload.
118///
119/// Returns a [`Revertible`] to revert the subscribe.
120///
121/// # Safety
122/// The passed event identifier must always come with valid data of the given type.
123pub unsafe fn event_subscribe_typed<T>(
124    identifier: impl AsRef<str>,
125    callback: RawEventConsume<T>,
126) -> Revertible<impl Fn() + Send + Sync + Clone + 'static> {
127    let callback =
128        unsafe { mem::transmute::<RawEventConsume<T>, RawEventConsumeUnknown>(callback) };
129    event_subscribe_unknown(identifier, callback)
130}
131
132/// Unsubscribes a previously registered raw event callback.
133pub fn event_unsubscribe(identifier: impl AsRef<str>, callback: RawEventConsumeUnknown) {
134    let identifier = str_to_c(identifier, "failed to convert event identifier");
135    let EventApi { unsubscribe, .. } = AddonApi::get().event;
136    unsafe { unsubscribe(identifier.as_ptr(), callback) }
137}
138
139/// Macro to wrap an event callback.
140///
141/// Generates a [`RawEventConsume`] wrapper around the passed callback.
142///
143/// # Usage
144/// ```no_run
145/// # use nexus::event::*;
146/// let event_callback = event_consume!(|data: Option<&i32>| {
147///     use nexus::log::{log, LogLevel};
148///     log(LogLevel::Info, "My Addon", format!("received event with data {data:?}"));
149/// });
150///
151/// let event_callback = event_consume!(<i32> |data| {
152///     use nexus::log::{log, LogLevel};
153///     log(LogLevel::Info, "My Addon", format!("received event with data {data:?}"));
154/// });
155/// ```
156///
157/// ```no_run
158/// # use nexus::event::*;
159/// fn event_callback(data: Option<&i32>) {
160///     use nexus::log::{log, LogLevel};
161///     log(LogLevel::Info, "My Addon", format!("Received event with data {data:?}"));
162/// }
163/// let event_callback = event_consume!(<i32> event_callback);
164/// ```
165///
166/// Note that the payload type corresponds to the pointee in Nexus documentation.
167/// If you are interested in the pointer itself, you have to cast the obtained reference back to a pointer:
168/// ```no_run
169/// # use nexus::event::*;
170/// use std::ffi::{c_char, CStr};
171///
172/// let event_callback = event_consume!(<c_char> |data| {
173///     if let Some(data) = data {
174///         let ptr = data as *const c_char;
175///         let c_str = unsafe { CStr::from_ptr(ptr) };
176///     }
177/// });
178/// ```
179#[macro_export]
180macro_rules! event_consume {
181    ( < $ty:ty > $callback:expr $(,)? ) => {{
182        const __CALLBACK: fn(::std::option::Option<&$ty>) = ($callback);
183
184        extern "C-unwind" fn __event_callback_wrapper(data: *const $ty) {
185            let _ = unsafe { ::std::mem::transmute::<*const $ty, *const ::std::ffi::c_void>(data) }; // size check
186            __CALLBACK(unsafe { data.as_ref() })
187        }
188
189        __event_callback_wrapper
190    }};
191    ( $ty:ty , $callback:expr $(,)? ) => {
192        $crate::event::event_consume!(<$ty> $callback)
193    };
194    ( | $arg:ident : Option<& $ty:ty > | $body:expr $(,)? ) => {
195        $crate::event::event_consume!(<$ty> |$arg: Option<& $ty >| $body)
196    };
197    ( $callback:expr $(,)? ) => {{
198        $crate::event::event_consume!(<()> $callback)
199    }};
200}
201
202pub use event_consume;
203
204/// Macro to subscribe to an event with a wrapped callback.
205///
206/// This macro is [unsafe](https://doc.rust-lang.org/std/keyword.unsafe.html).
207/// See [`event_subscribe_typed`] for more information.
208///
209/// Returns a [`Revertible`] to revert the subscribe.
210///
211/// # Usage
212/// ```no_run
213/// # use nexus::event::*;
214/// unsafe {
215///     event_subscribe!("MY_EVENT" => i32, |data| {
216///         use nexus::log::{log, LogLevel};
217///         log(LogLevel::Info, "My Addon", format!("Received MY_EVENT with {data:?}"));
218///     })
219/// }.revert_on_unload();
220/// ```
221///
222/// The event identifier may be dynamic and the callback can be a function name.
223/// ```no_run
224/// # use nexus::event::*;
225/// let event: &str = "MY_EVENT";
226/// fn event_callback(data: Option<&i32>) {
227///     use nexus::log::{log, LogLevel};
228///     log(LogLevel::Info, "My Addon", format!("Received MY_EVENT with {data:?}"));
229/// }
230/// let revertible = unsafe { event_subscribe!(event => i32, event_callback) };
231/// revertible.revert();
232/// ```
233///
234/// The `unsafe` keyword can be moved into the macro call:
235/// ```no_run
236/// # use nexus::event::*;
237/// # fn event_callback(_: Option<&()>) {}
238/// event_subscribe!(unsafe "MY_EVENT" => (), event_callback);
239/// ```
240/// Note that the payload type corresponds to the pointee in Nexus documentation.
241/// If you are interested in the pointer itself, you have to cast the obtained reference back to a pointer:
242/// ```no_run
243/// # use nexus::event::*;
244/// use std::ffi::{c_char, CStr};
245///
246/// event_subscribe!(unsafe "EV_ACCOUNT_NAME" => c_char, |data| {
247///     if let Some(data) = data {
248///         let ptr = data as *const c_char;
249///         let c_str = unsafe { CStr::from_ptr(ptr) };
250///     }
251/// });
252/// ```
253///
254/// # Safety
255/// See [`event_subscribe_typed`].
256#[macro_export]
257macro_rules! event_subscribe {
258    ( unsafe $event:expr , $ty:ty , $callback:expr $(,)? ) => {
259        unsafe { $crate::event::event_subscribe!($event => $ty, $callback) }
260    };
261    ( unsafe $event:expr => $ty:ty , $callback:expr $(,)? ) => {
262        unsafe { $crate::event::event_subscribe!($event => $ty, $callback) }
263    };
264    ( $event:expr , $ty:ty , $callback:expr $(,)? ) => {
265        $crate::event::event_subscribe!($event => $ty, $callback)
266    };
267    ( $event:expr => $ty:ty , $callback:expr $(,)? ) => {
268        $crate::event::event_subscribe_typed($event, $crate::event::event_consume!(<$ty> $callback))
269    };
270}
271
272pub use event_subscribe;
273
274/// Raises an event to all subscribing addons.
275///
276/// # Safety
277/// The passed event identifier must be associated with data of the given type.
278pub unsafe fn event_raise<T>(identifier: impl AsRef<str>, event_data: &T) {
279    let identifier = str_to_c(identifier, "failed to convert event identifier");
280    let data: *const _ = event_data;
281    let EventApi { raise, .. } = AddonApi::get().event;
282    unsafe { raise(identifier.as_ptr(), data.cast()) }
283}
284
285/// Raises an event without payload to all subscribing addons.
286pub fn event_raise_notification(identifier: impl AsRef<str>) {
287    let identifier = str_to_c(identifier, "failed to convert event identifier");
288    let EventApi {
289        raise_notification, ..
290    } = AddonApi::get().event;
291    unsafe { raise_notification(identifier.as_ptr()) }
292}
293
294/// Raises an event for a specific subscribing addon.
295///
296/// # Safety
297/// See [`event_raise`].
298pub unsafe fn event_raise_targeted<T>(signature: i32, identifier: impl AsRef<str>, event_data: &T) {
299    let identifier = str_to_c(identifier, "failed to convert event identifier");
300    let data: *const _ = event_data;
301    let EventApi { raise_targeted, .. } = AddonApi::get().event;
302    unsafe { raise_targeted(signature, identifier.as_ptr(), data.cast()) }
303}
304
305/// Raises an event without payload for a specific subscribing addon.
306pub fn event_raise_notification_targeted(signature: i32, identifier: impl AsRef<str>) {
307    let identifier = str_to_c(identifier, "failed to convert event identifier");
308    let EventApi {
309        raise_notification_targeted,
310        ..
311    } = AddonApi::get().event;
312    unsafe { raise_notification_targeted(signature, identifier.as_ptr()) }
313}